import * as THREE from 'three';
import { ThreeRender } from '@/components/Warm/SDK/three';
// @ts-ignore
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { Vector2 } from 'three';
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { UnrealBloomPass } from "three/examples/jsm/postprocessing/UnrealBloomPass.js";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
// import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
// import { CopyShader } from "three/examples/jsm/shaders/CopyShader.js";
import { OutputPass } from "three/examples/jsm/postprocessing/OutputPass.js";
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

// @ts-ignore
import SRTC from '@/assets/2.mp3';

export class Node {
    private lastStrength = 0;
    private theStrength = 0;
    private targetRange = 0;
    private _range = 0;
    private baseRange: any;
    private angle: any;
    private center: any;

    constructor(baseRange: number, angle: number, center: THREE.Vector2) {
        this.baseRange = baseRange;
        this.angle = angle;
        this.center = center;
    }

    positionA() {
        const range = this._range + this.baseRange;
        const x = Math.cos(this.angle * Math.PI / 180) * range;
        const y = Math.sin(this.angle * Math.PI / 180) * range;
        return new Vector2(this.center.x + x, this.center.y + y);
    }

    positionB() {
        const range = this._range * -1 + this.baseRange;
        const x = Math.cos(this.angle * Math.PI / 180) * range;
        const y = Math.sin(this.angle * Math.PI / 180) * range;
        return new Vector2(this.center.x + x, this.center.y + y);
    }

    strength(newStrength: number) {
        this.lastStrength = this.theStrength;
        this.theStrength = newStrength;
        this.targetRange = Math.max(this.theStrength - this.lastStrength, 0);
        if (this.targetRange > this._range) this._range = this.targetRange;
    }

    transition(delay: number) {
        this._range = Math.max(this._range - delay * this._range * 5, 0);
    }
}

type MusicConfigType = {
    isOpenGUI?: boolean,
    useCanvasHeightAndWidth?: boolean
}

export class Music extends ThreeRender {
    private GUI_CONFIG = {
        R: 20,
        G: 90,
        B: 225,
        TrianglesBgColor: 0x03a9f4,
        TrianglesLineColor: 0x03a9f4,
        lineColor: 0x00ffff,
        rotate: false
    }
    private MUSIC_CONFIG = {
        N: 256,
        scale: 0.1
    }
    // 辉光
    private BLOOM_PASS_CONFIG = {
        exposure: 1,
        bloomStrength: 1,
        bloomThreshold: 0,
        bloomRadius: 0
    }
    private mixer: THREE.AnimationMixer | null = null;// 动画对象
    private audio: THREE.Audio | null = null; // 音频
    private analyser: THREE.AudioAnalyser | null = null; // 音葉數據
    private linesGroup: THREE.Group | null = null;
    private outLine: any;
    private inLine: THREE.Line | null = null;
    private barLine: any[] = [];
    private barNodes: Node[] | any[] = [];
    private barGroup: any;
    private composer: EffectComposer | null = null;
    private controls: OrbitControls;
    private bloomPass: UnrealBloomPass | null = null;

    constructor(el: Element, config?: MusicConfigType) {
        super(el, { useCanvasHeightAndWidth: !!config?.useCanvasHeightAndWidth });
        this.controls = new OrbitControls(this.camera, this.renderer.domElement);
        this.initModel(() => {
            config?.isOpenGUI && this.setGui();
            this.setBloomPass();
            this.initMusic();
            this.setControls();
            this.setCamera();
            this.setLight();
            this.setRenderer();
        });
    }


    private initModel(callback: Function) {
        new GLTFLoader().load('/models/PrimaryIonDrive.glb', (gltf) => {

            const model = gltf.scene;

            this.scene.add(model);

            this.mixer = new THREE.AnimationMixer(model);

            const clip = gltf.animations[0];

            this.mixer.clipAction(clip.optimize()).play();

            callback();
        });
    }

    private initMusic() {
        this.createAudioLines(20, this.MUSIC_CONFIG.N);

        this.createAudioBars(25, this.MUSIC_CONFIG.N / 2); // 添加音频柱子
        // // 加载音频 start
        const listener = new THREE.AudioListener(); // 监听者

        this.linesGroup!.rotation.set(-10, 0, 0);

        this.barGroup.rotation.set(-10, 0, 0);

        this.audio = new THREE.Audio(listener); // 非位置音频对象

       // this.audioLoad(SRTC);

        this.animate();
    }

    private setLight() {
        this.scene.add(new THREE.AmbientLight(0xcccccc));
        let light = new THREE.PointLight(0xffffff, 1);
        this.scene.add(light);
        //告诉平行光需要开启阴影投射
        //light.castShadow = true;
        // light.position.y = 20;
        // const light = new THREE.DirectionalLight( 0xffffff, 2 );
        // light.position.set( 20, -50, 20 );
        // light.castShadow = true;
        // light.shadow.mapSize.width = 1024;
        //
        // light.shadow.mapSize.height = 1024;
        // this.scene.add(light);
    }

    private setScene() {
        // 设置场景背景色
        // this.scene!.background = new THREE.Color('skyblue');
    }

    private setRenderer() {
        const age: number = 2;
        console.log(age);
        this.renderer.setClearAlpha(0);
        // this.scene.background = new THREE.CubeTextureLoader().load([
        //     r,l,t,b,f,b2
        // ]);
        // 色调映射
        this.renderer!.toneMapping = THREE.ReinhardToneMapping;
        // 色调曝光度
        this.renderer!.toneMappingExposure = 1.25;
        // 是否开启阴影渲染
        this.renderer!.shadowMap.enabled = true;
        // 阴影类型
        this.renderer!.shadowMap.type = THREE.PCFSoftShadowMap;
        // 输出编码格式
        this.renderer!.outputColorSpace = THREE.SRGBColorSpace;
        // 在renderer里面设置背景色
        this.renderer!.setClearColor(0xffffff, 0);
    }

    private setControls() {
        // 使动画循环使用时阻尼或自转 意思是否有惯性
        this.controls.enableDamping = true;
        //动态阻尼系数 就是鼠标拖拽旋转灵敏度
        this.controls.dampingFactor = 0.25;
        //是否可以缩放
        this.controls.enableZoom = true;
        //设置相机距离原点的最远距离
        this.controls.minDistance = 1;
        //设置相机距离原点的最远距离
        this.controls.maxDistance = 200;
        //是否开启右键拖拽
        this.controls.enablePan = false;
    }

    private setCamera() {
        // 设置相机
        this.camera!.position.set(-1, 2.5, 0);
        // 设置观看中心点
        this.camera!.lookAt(0, 0, 0);
    }

    // GUI控制显示
    private setGui() {
        //声明一个保存需求修改的相关数据的对象
        let _that = this;
        let datGui = new GUI();
        //将设置属性添加到gui当中，gui.add(对象，属性，最小值，最大值）
        datGui.add(this.GUI_CONFIG, "R", 0, 255);
        datGui.add(this.GUI_CONFIG, "G", 0, 255);
        datGui.add(this.GUI_CONFIG, "B", 0, 255);
        datGui.add(this.GUI_CONFIG, "rotate").onChange(function(val: any) {
            _that.controls.autoRotate = val;
        });
        datGui.addColor(this.GUI_CONFIG, "lineColor").onChange(function() {
            _that.linesGroup!.traverse(function(child: any) {
                if (child.isLine)
                    child.material = new THREE.LineBasicMaterial({
                        color: _that.GUI_CONFIG.lineColor
                    });
            });
        });

        const bloomFolder = datGui.addFolder('bloom');

        bloomFolder.add(this.BLOOM_PASS_CONFIG, 'bloomThreshold', 0.0, 1.0).onChange((value: any) => {
            this.bloomPass!.threshold = Number(value);
        })

        bloomFolder.add(this.BLOOM_PASS_CONFIG, 'bloomStrength', 0.0, 3.0 ).onChange( (value: any) => {
            this.bloomPass!.strength = Number( value );
        } );

        datGui.add(this.BLOOM_PASS_CONFIG, 'bloomRadius', 0.0, 1.0 ).step( 0.01 ).onChange((value: any) => {
            this.bloomPass!.radius = Number(value);
        });
    }

    private range(start: number, end: number, step = 1, fromRight = false) {
        let index = -1,
            length = Math.max(Math.ceil((end - start) / step), 0),
            result = Array(length);
        while (length--) {
            result[fromRight ? length : ++index] = start;
            start += step;
        }
        return result;
    }

    // 选择音频
    public playAudio(ref: any) {
        console.log(ref);
        let _that = this;
        let objFile = ref;
        if (objFile.value === "") {
            return false;
        }
        if (window.FileReader) {
            let reader = new FileReader();
            reader.readAsDataURL(objFile.files[0]);
            reader.onloadend = function(e: any) {
                _that.audioLoad(e.target.result);
            };
        }
    }

    private renderGeometries(vertices: any) {
        const res: any[] = [];
        vertices = vertices.concat(vertices[0]);
        vertices.forEach((value: any) => {
            res.push(value.x, value.y, 0);
        });
        return new THREE.BufferAttribute(new Float32Array(res), 3);
    }

    /**
     * 创建音频线
     * @param radius 半径
     * @param countData 线圈总数
     * @private
     */
    private createAudioLines(radius: number, countData: number) {
        this.barNodes = this.range(0, countData).map(index => {
            return new Node(
                radius,
                ((index / countData) * 360 + 45) % 360,
                new THREE.Vector2(0, 0)
            );
        });
        const lineMaterial = new THREE.LineBasicMaterial({
            color: this.GUI_CONFIG.lineColor
        });
        this.barLine = this.range(0, countData).map(index => {
            return new THREE.Line(
                new THREE.BufferGeometry().setAttribute(
                    "position",
                    this.renderGeometries([
                        this.barNodes[index].positionA(),
                        this.barNodes[index].positionB()
                    ])
                ),
                lineMaterial
            );
        });
        this.outLine = new THREE.Line(
            new THREE.BufferGeometry().setAttribute(
                "position",
                this.renderGeometries(this.barNodes.map((node: Node) => node.positionA()))
            ),
            lineMaterial
        );

        this.inLine = new THREE.Line(
            new THREE.BufferGeometry().setAttribute(
                "position",
                this.renderGeometries(this.barNodes.map((node: Node) => node.positionB()))
            ),
            lineMaterial
        );

        this.linesGroup = new THREE.Group();
        this.linesGroup.add(this.outLine);
        this.linesGroup.add(this.inLine);
        this.barLine.forEach(line => this.linesGroup!.add(line));
        this.linesGroup.scale.set(this.MUSIC_CONFIG.scale, this.MUSIC_CONFIG.scale, this.MUSIC_CONFIG.scale);
        this.scene.add(this.linesGroup);
    }


    /**
     * 创建柱子
     * @param radius 半径
     * @param countData 柱子总数
     * @private
     */
    private createAudioBars(radius: number, countData: number) {
        this.barGroup = new THREE.Group();
        let R = radius;
        let N = countData;
        for (let i = 0; i < N; i++) {
            let minGroup = new THREE.Group();
            let box = new THREE.BoxGeometry(1, 1, 1);
            let material = new THREE.MeshPhongMaterial({
                color: 0x00ffff
            }); // 材质对象
            let m = i;
            let mesh = new THREE.Mesh(box, material);
            mesh.position.y = 0.5;
            minGroup.add(mesh);
            minGroup.position.set(
                Math.sin(((m * Math.PI) / N) * 2) * R,
                Math.cos(((m * Math.PI) / N) * 2) * R,
                0
            );
            minGroup.rotation.z = ((-m * Math.PI) / N) * 2;
            this.barGroup.add(minGroup);
        }
        this.scene.add(this.barGroup);
    }

    //  音频加载播放
    private audioLoad(url: string) {
        let _that = this;
        let audioLoader = new THREE.AudioLoader(); // 音频加载器
        audioLoader.load(url, function(AudioBuffer) {
            if (_that.audio!.isPlaying) {
                _that.audio!.stop();
                _that.audio!.setBuffer(AudioBuffer);
            }
            _that.audio!.setBuffer(AudioBuffer); // 音频缓冲区对象关联到音频对象audio
            _that.audio!.setLoop(true); //是否循环
            _that.audio!.setVolume(1); //音量
            _that.audio!.play(); //播放
            // 音频分析器和音频绑定，可以实时采集音频时域数据进行快速傅里叶变换
            _that.analyser = new THREE.AudioAnalyser(_that.audio!, _that.MUSIC_CONFIG.N * 2);
        });
    }

    // 辉光
    private setBloomPass() {

        let renderScene = new RenderPass(this.scene, this.camera);

        const { height, width } = this.getHeightAndWidth();

        this.bloomPass = new UnrealBloomPass(
            new THREE.Vector2(width, height),
            1.5,
            0.4,
            0.85
        );

        this.bloomPass.threshold = this.BLOOM_PASS_CONFIG.bloomThreshold;

        this.bloomPass.strength = this.BLOOM_PASS_CONFIG.bloomStrength;

        this.bloomPass.radius = this.BLOOM_PASS_CONFIG.bloomRadius;

        this.composer = new EffectComposer(this.renderer);

        //const copyShader = new ShaderPass(CopyShader);
        const outputPass = new OutputPass();

        this.composer.addPass(renderScene);
        this.composer.addPass(this.bloomPass);
        this.composer.addPass(outputPass);
        // 辉光 end
    }

    // 动态渲染
    private animate() {
        if (this.analyser) {
            this.controls.update();
            // 获得频率数据N个
            let arr = this.analyser.getFrequencyData();
            if (this.barGroup) {
                this.barGroup.rotation.z += 0.002;
                this.barGroup.scale.set(this.MUSIC_CONFIG.scale, this.MUSIC_CONFIG.scale, this.MUSIC_CONFIG.scale);
                this.barGroup.children.forEach((elem: any, index: any) => {
                    if (this.GUI_CONFIG.R) {
                        elem.children[0].material.color.r = arr[index] / (this.GUI_CONFIG.R * 3);
                    }
                    if (this.GUI_CONFIG.G) {
                        elem.children[0].material.color.g = arr[index] / (this.GUI_CONFIG.G * 3);
                    }
                    if (this.GUI_CONFIG.B) {
                        elem.children[0].material.color.b = arr[index] / (this.GUI_CONFIG.B * 3);
                    }
                    if (arr[index] === 0) {
                        elem.scale.set(0, 0, 0);
                    } else {
                        let m = arr[index] / 20;
                        let targetRange = Math.max(
                            arr[index] / 20 - arr[index - 1] / 20,
                            0
                        );
                        if (m < targetRange) {
                            m = targetRange;
                        }
                        elem.scale.set(1, m, 1);
                    }
                });
            }
            const Delta = this.clock.getDelta();

            this.mixer!.update(Delta);

            this.barNodes.forEach((node: any, index: any, array: any) => {
                node.strength(arr[index % array.length] * 0.1);
                node.transition(Delta);
            });
            this.MUSIC_CONFIG.scale = 0.1 + arr[Math.ceil(arr.length * 0.05)] / 500;
            this.updateCircle();
        }

        //this.renderer.render(this.scene, this.camera);
        this.composer!.render();
        requestAnimationFrame(this.animate.bind(this));
    }

    private updateCircle() {
        if (this.barNodes) {
            this.linesGroup!.scale.set(this.MUSIC_CONFIG.scale, this.MUSIC_CONFIG.scale, this.MUSIC_CONFIG.scale);
            const geometryA = this.outLine.geometry;
            const AttributeA = geometryA.getAttribute("position");
            const geometryB: any = this.inLine!.geometry;
            const AttributeB: any = geometryB.getAttribute("position");

            const positions = this.barNodes.map((value: any) => {
                return [value.positionA(), value.positionB()];
            });
            positions.forEach((position: any, index: any) => {
                AttributeA.set([position[0].x, position[0].y], index * 3);
                AttributeB.set([position[1].x, position[1].y], index * 3);
                const geometry = this.barLine[index].geometry;
                const Attribute = geometry.getAttribute("position");
                Attribute.set(
                    [position[0].x, position[0].y, 0, position[1].x, position[1].y, 0],
                    0
                );
                Attribute.needsUpdate = true;
            });
            AttributeA.set(
                [AttributeA.array[0], AttributeA.array[1]],
                positions.length * 3
            );
            AttributeB.set(
                [AttributeB.array[0], AttributeB.array[1]],
                positions.length * 3
            );
            AttributeA.needsUpdate = true;
            AttributeB.needsUpdate = true;
        }
    }
}